package autotd.gui;
import autotd.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

import java.io.*;
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;




class SimPanel extends JPanel implements ComponentListener, MouseMotionListener, AdvancedSimulatorListener {
	
	private static final Color backgroundcolor = Color.black;
	private static final Color groundcolor = new Color((float)0.3,(float)0.7,(float)0.2);
	private static final Color pathcolor = new Color((float)1.0,(float)1.0,(float)0.5);
	private static final Color rangecolor = new Color(0.f,0.f,1.f,0.1f);
	private static final Color mouseovercolor = Color.black;
	
	private boolean firstpaint = true;
	
	private double panelwidth;
	private double panelheight;
	private int gridwidth = 1;
	private int gridheight = 1;
	private double tilesize;
	private double offsettop;	// position of the centre of the topmost row
	private double offsetleft;	// position of the centre of the leftmost column
	
	
	private TDSimulator sim;
	private GameMap map;
	private Point selectedPoint = null;
	private Point overedPoint = new Point(-1,-1);
	private TowerType toplace;
	
	private Random random;
	private HashMap<MonsterType,Color> monsterColors = new HashMap<MonsterType,Color>();
	private HashMap<TowerType,Color> towerColors = new HashMap<TowerType,Color>();
	private Color randomDarkColor(Random r) {
		return new Color(r.nextFloat()*0.5f,r.nextFloat()*0.5f,r.nextFloat()*0.5f);
	}
	private Color lighter(Color c) {
		return new Color(c.getRed()+127,c.getGreen()+127,c.getBlue()+127);
	}
	
	private int previousX = 0, previousY = 0;
	private boolean holdTowerInfo = false, holdMonsterInfo = false;
	
	
	private enum State {
		none,	// nothing selected
		tower,	// tower selected
		place}	// waiting to place a tower
	private enum Location {
		abyss,	// not on map
		tower,	// location of a tower, or where it is valid to place one
		path	// on the path
		}
	State current = State.none;
	Location here;
	
	public SimPanel() {
		super.setPreferredSize(new Dimension(800,600));
		this.addComponentListener(this);
		this.addMouseListener(new ML());
		this.addMouseMotionListener(this);
		fixWidthHeight();
	}
	
	public State getState() {
		return current;
	}
	
	public void setSimulator(TDSimulator s) {
		sim = s;
		if(sim == null) return;
		sim.addAdvancedListener(this);
		random = new Random(11072009);	// seed with date of tourney
		for (TowerType tt: sim.getTowerTypes()) {
			towerColors.put(tt,randomDarkColor(random));
		}
		for (MonsterType tt: sim.getMonsterTypes()) {
			monsterColors.put(tt,randomDarkColor(random));
		}
		map = sim.getMap();
		gridwidth = map.getWidth();
		gridheight = map.getLength();
		fixWidthHeight();
		repaint();
	}
	
	// component listener methods
	public void componentHidden(ComponentEvent e) {} //Invoked when the component has been made invisible.
	public void componentMoved(ComponentEvent e) {} //Invoked when the component's position changes.
	public void componentShown(ComponentEvent e) {} //Invoked when the component has been made visible.
	//Invoked when the component's size changes.
	public void componentResized(ComponentEvent e) {
		fixWidthHeight();
	}
	public boolean fixWidthHeight() {
		Dimension d = this.getSize();
		panelwidth = d.getWidth();
		panelheight = d.getHeight();
		double maxtilewidth = panelwidth/(gridwidth+2.0);	// have space for map plus a spacing of 1 tile on either side
		double maxtileheight = panelheight/(gridheight+2.0);	// have space for map plus a spacing of 1 tile on either side
		tilesize = (int)Math.min(maxtilewidth,maxtileheight);
		offsetleft = (panelwidth-gridwidth*tilesize)/2.0;
		offsettop = (panelheight-gridheight*tilesize)/2.0;
		repaint();
		return panelwidth>0.;
	}
	
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2d = (Graphics2D)g;
		
		g2d.setColor(backgroundcolor);
		g2d.fill(new Rectangle2D.Double(0,0,panelwidth,panelheight));
		
		drawMap(g2d);
	}
	public void drawMap(Graphics2D g2d) {
		
		if(sim == null) return;
		
		// draw background
		for(int y = 0; y < gridheight; y++) {
			for(int x = 0; x < gridwidth; x++) {
				drawSquare(g2d,new Point(x,y));
			}
		}
		
		// draw monsters and towers, from back to front
		for(int y = 0; y < gridheight; y++) {
			for(int x = 0; x < gridwidth; x++) {
				drawUnits(g2d,new Point(x,y));
			}
		}
		
		drawLines(g2d);
		
		if (map.isValid(overedPoint)) {
			setPositionAndWidth(gridsquare,getCanvasPoint(overedPoint),1.0);
			g2d.setColor(mouseovercolor);
			g2d.draw(gridsquare);
		}
	}
	
	Rectangle2D gridsquare = new Rectangle2D.Double();	// instance variable so we dont have to instantiate a new rect for every draw
	public void drawSquare(Graphics2D g2d, Point p) {
		setPositionAndWidth(gridsquare,getCanvasPoint(p),1.0);
		Color gcolor;
		if(map.isPath(p))
			gcolor = pathcolor;
		else
			gcolor = groundcolor;
		
		g2d.setColor(gcolor);
		g2d.fill(gridsquare);
		
		if (selectedPoint!=null) {
			for (Tower t: map.getTowerStack(selectedPoint)) {
				if (t.ready() && t.inRange(p)) {
					g2d.setColor(rangecolor);
					g2d.fill(gridsquare);
				}
			}
		}
	}
	public void drawUnits(Graphics2D g2d, Point p) {
		if (map.isPath(p)) {
			drawMonsters(g2d,p);
		} else {
			drawTowers(g2d,p);
		}
	}
	public void drawTowers(Graphics2D g2d, Point p) {
		List<Tower> stack = sim.getMap().getTowerStack(p);
		for (int i = 0; i < stack.size(); i++) {
			drawTower(g2d,p,i,stack.get(i));
		}
	}
	public void drawMonsters(Graphics2D g2d, Point p) {
		List<Monster> stack = sim.getMap().getMonsterStack(p);
		for (int i = 0; i < stack.size(); i++) {
			drawMonster(g2d,p,i,stack.get(i));
		}
	}
	Rectangle2D tower = new Rectangle2D.Double();
	public void drawTower(Graphics2D g2d, Point p, int index, Tower t) {
		double size = Math.pow(0.8,index+1);	// make higher towers smaller, so we can see how many towers are on the square
		setPositionAndWidth(tower,getCanvasPoint(p),size);
		g2d.setColor(lighter(towerColors.get(t.Type)));
		g2d.fill(tower);
		if (t.ready()) g2d.setColor(towerColors.get(t.Type));
		else g2d.setColor(Color.red);
		g2d.draw(tower);
	}
	Ellipse2D monster = new Ellipse2D.Double();
	public void drawMonster(Graphics2D g2d, Point p, int index, Monster m) {
		setPositionAndWidth(monster,getMonsterPosition(m),0.1);
		
		g2d.setColor(lighter(monsterColors.get(m.Type)));
		g2d.fill(monster);
		g2d.setColor(monsterColors.get(m.Type));
		g2d.draw(monster);
	}
	
	HashMap<Integer,Point2D.Double> monsterpositions = new HashMap<Integer,Point2D.Double>();
	public Point2D.Double getMonsterPosition(Monster m) {
		if (!monsterpositions.containsKey(m.id)) {
			// random position in the square for each monster, so you can see approx how many monsters there are
			double dx = tilesize*(random.nextDouble()-0.5);
			double dy = tilesize*(random.nextDouble()-0.5);
			monsterpositions.put(m.id, new Point2D.Double(dx,dy));
		}
		Point2D.Double centre = getCanvasPoint(m.pos);
		Point2D.Double offset = monsterpositions.get(m.id);
		Point2D.Double randomcentre = new Point2D.Double(centre.x+offset.x,centre.y+offset.y);
		return randomcentre;
	}
	// bullet fire lines
	private int timeOfFire = -1;
	ArrayList<Line2D> lines = new ArrayList<Line2D>();
	public void drawLines(Graphics2D g2d) {
		if (timeOfFire!=sim.getTurnNumber()) {
			lines = new ArrayList<Line2D>();	// if it is a different turn, expire lines of fire
			timeOfFire = sim.getTurnNumber();
		}
		g2d.setColor(Color.red);
		for (Line2D l: lines) {
			g2d.draw(l);
		}
	}
	
	
	public void setPositionAndWidth(RectangularShape r, Point2D.Double centre, double sizefrac) {
		Point2D.Double corner = new Point2D.Double(
			centre.x+tilesize*sizefrac*0.5,
			centre.y+tilesize*sizefrac*0.5);
		r.setFrameFromCenter(centre,corner);
	}
	
	/** Convert a grid point to a canvas point */
	public Point2D.Double getCanvasPoint(Point p) {
		double x = offsetleft+tilesize*(p.x+0.5);
		double y = offsettop+tilesize*(p.y+0.5);
		return new Point2D.Double(x,y);
	}
	
	/** Convert a canvas point to a grid point */
	public Point getGridPoint(Point2D p) {
		int x = (int)Math.floor((p.getX()-offsetleft)/tilesize);
		int y = (int)Math.floor((p.getY()-offsettop)/tilesize);
		return new Point(x,y);
	}
	
	
	private void selectTower(Point p) {
		GUI.log("Selecting tower...");
		selectedPoint = p;
		if (p==null || map.getTowerStack(p).size()==0) {
			current = State.none;
			GUI.log("... no tower");
			holdTowerInfo = false;
			updateTowerInfo(p);
		} else {
			current = State.tower;
			GUI.log("... "+selectedPoint);
			holdTowerInfo = true;
			updateTowerInfo(p);
		}
	}
	
	private void fire(Point p) {
		GUI.log("Firing...");
		List<Monster> stack = sim.getMap().getMonsterStack(p);
		if (stack.size()>=1) {
			Monster mons = stack.get(0);
			GUI.log("... at "+mons);
			boolean success = sim.shootMonster(selectedPoint,mons);
			if (success) lines.add(new Line2D.Double(getCanvasPoint(selectedPoint), getMonsterPosition(mons)));
		} else {
			GUI.log("... at nothing.");
			// no monsters there
		}
	}
	
	public void setPlaceTower(TowerType tt) {
		if (tt==null)
			throw new IllegalArgumentException("can't have a null TowerType");
		toplace = tt;
		current = State.place;
		GUI.log("Waiting for player to place tower of type: "+toplace);
	}
	
	private class ML extends MouseAdapter {
		public void mouseClicked(MouseEvent e) {
			Point p = getGridPoint(e.getPoint());
			
			GUI.log("Clicked on "+p);
			
			here = Location.tower;
			if (!sim.getMap().isValid(p)) {
				here = Location.abyss;
				updateTowerInfo(p);
				GUI.log("Deselected");
			}
			else if (map.isPath(p))
				here = Location.path;
			
			// state machine
			switch(current) {
			case none:
				switch(here) {
				case abyss:
					current = State.none;
					holdTowerInfo = false;
				break;
				case tower:
					selectTower(p);
				break;
				case path:
					current = State.none;
				break;
				}
			break;
			case tower:
				switch(here) {
				case abyss:
					current = State.none;
					holdTowerInfo = false;
				break;
				case tower:
					selectTower(p);
				break;
				case path:
					fire(p);
					current = State.tower;
				break;
				}
			break;
			case place:
				switch(here) {
				case abyss:
					current = State.none;
					holdTowerInfo = false;
				break;
				case tower:
					sim.placeTower(toplace,p.x,p.y);
					GUI.log("Placed tower at "+p);
					//selectTower(x,y);
					updateTowerInfo(p);
					current = State.none;
				break;
				case path:
					current = State.none;
				break;
				}
			break;
			}
			GUI.gui.repaint();
		}
	}

	public void mouseDragged(MouseEvent e) {
			
	}

	public void mouseMoved(MouseEvent e) {
		
		if(sim == null)
			return;
		
		Point p = getGridPoint(e.getPoint());
		
		if(!overedPoint.equals(p)) {
			if(!holdTowerInfo) {
				updateTowerInfo(p);
			}
			if(!holdMonsterInfo) {
				updateMonsterInfo(p);
			}
		}
		
		overedPoint = p;
		repaint();
	}
	
	private void updateTowerInfo(Point p) {
		GUI.gui.infoPanel.setTowerStackPoint(p);
	}
	
	private void updateMonsterInfo(Point p) {
		GUI.gui.infoPanel.setMonsterList(sim.getMap().getMonsterStack(p));
	}
	
	public void monsterCreated(int mID, String type, int level, int x, int y) {
		repaint();
	}
	public void monsterMoved(int mID, int prevX, int prevY, int currX, int currY) {
		repaint();
	}
	public void monsterInjured(int mID, int health) {
		repaint();
	}
	public void monsterCompletedPath(int mID) {
		repaint();
	}
	public void towerPlaced(int tID, String type, int x, int y, int height) {
		repaint();
	}
	public void towerFired(int tID, int mID) {
		repaint();
	}
	public void playerChange(int lives, int money) {}
	public void turnChangedTo(int turnNum) {}
}



























